package vip.liux.backend.application.serviceImpl.gen;

import com.github.benmanes.caffeine.cache.Cache;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.jdbc.core.simple.JdbcClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ClassUtils;
import vip.liux.application.dots.ListResultDto;
import vip.liux.application.dots.PagedResultDto;
import vip.liux.application.dots.PagedResultRequestDto;
import vip.liux.backend.application.service.gen.*;
import vip.liux.backend.domain.models.gen.GenDataSource;
import vip.liux.backend.domain.models.gen.GenTable;
import vip.liux.backend.domain.models.gen.GenTableColumn;
import vip.liux.backend.infrastructure.jpa.gen.GenDataSourceMapper;
import vip.liux.backend.infrastructure.jpa.gen.GenTableColumnMapper;
import vip.liux.backend.infrastructure.jpa.gen.GenTableMapper;
import vip.liux.cache.GlobalCache;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.StringWriter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static vip.liux.application.PageKit.toPage;
import static vip.liux.application.PageKit.toPaged;
import static vip.liux.backend.application.serviceImpl.gen.FreeMarkerUtils.CONFIGURATION;
import static vip.liux.backend.application.serviceImpl.gen.GenTableUtils.buildTableColumn;
import static vip.liux.backend.application.serviceImpl.gen.GenTableUtils.initTable;
import static vip.liux.backend.infrastructure.constants.GenConst.*;

@Service
public class GenTableServiceImpl implements GenTableService {

    private static final Logger logger = LoggerFactory.getLogger(GenTableServiceImpl.class);

    private final GenDataSourceMapper dataSourceMapper;
    private final GenTableMapper genTableMapper;
    private final GenTableColumnMapper genTableColumnMapper;
    private final Cache<String, Object> cache = GlobalCache.getCache(DATA_SOURCE_CACHE);

    public GenTableServiceImpl(GenDataSourceMapper dataSourceMapper, GenTableMapper genTableMapper, GenTableColumnMapper genTableColumnMapper) {
        this.dataSourceMapper = dataSourceMapper;
        this.genTableMapper = genTableMapper;
        this.genTableColumnMapper = genTableColumnMapper;
    }

    /**
     * 同步数据库表信息
     *
     * @param databaseId 数据源ID
     */
    @Override
    public void sync(Long databaseId) {
        GenDataSource dataSource = getDataSource(databaseId);
        JdbcClient client = (JdbcClient) cache.get(Objects.requireNonNull(dataSource.getId()).toString(), key -> createJdbcClient(dataSource));

        // 查询所有待同步的表信息
        List<TableDto> tableDtoList = client.sql(QUERY_ALL_TABLE_SQL).query(TableDto.class).list();
        tableDtoList.forEach(tableDto -> {
            GenTable genTable = initTable(tableDto);
            genTableMapper.save(genTable);

            List<ColumnDto> columns = client.sql(QUERY_COLUMN_SQL)
                    .param("tableName", tableDto.tableName())
                    .query(ColumnDto.class)
                    .list();

            columns.forEach(columnDto -> genTableColumnMapper.save(buildTableColumn(genTable.getId(), columnDto)));
        });
    }

    /**
     * 同步指定表信息
     *
     * @param tableNames 表名列表
     */
    @Transactional
    public void syncDb(List<String> tableNames) {
        GenDataSource dataSource = getDataSource(1L);
        JdbcClient client = (JdbcClient) cache.get(Objects.requireNonNull(dataSource.getId()).toString(), key -> createJdbcClient(dataSource));

        List<TableDto> tableDtoList = client.sql(QUERY_TABLE_SQL)
                .param("tableNames", tableNames)
                .query(TableDto.class)
                .list();

        tableDtoList.forEach(tableDto -> {
            GenTable genTable = initTable(tableDto);
            genTableMapper.save(genTable);

            List<ColumnDto> columns = client.sql(QUERY_COLUMN_SQL)
                    .param("tableName", tableDto.tableName())
                    .query(ColumnDto.class)
                    .list();

            columns.forEach(columnDto -> genTableColumnMapper.save(buildTableColumn(genTable.getId(), columnDto)));
        });
    }

    /**
     * 分页查询表信息
     *
     * @param input 分页请求参数
     * @return 分页结果
     */
    @Override
    public PagedResultDto<GenTable> pageList(PagedResultRequestDto input) {
        return toPaged(genTableMapper.findAll(toPage(input)));
    }

    /**
     * 获取表详情
     *
     * @param id 表ID
     * @return 表详情
     */
    @Override
    public GenTable detail(Long id) {
        return genTableMapper.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("未找到指定的表信息，ID：" + id));
    }

    /**
     * 删除表信息
     *
     * @param id 表ID
     */
    @Override
    public void delete(Long id) {
        genTableMapper.deleteById(id);
    }

    /**
     * 获取所有表信息
     *
     * @return 表信息列表
     */
    @Override
    public ListResultDto<GenTable> list() {
        List<GenTable> tables = genTableMapper.findAll();
        return new ListResultDto<>(tables);
    }

    @Override
    public Map<String, String> preview(Long tableId) {
        Map<String, String> dataMap = new LinkedHashMap<>();
        try {
            GenTable table = genTableMapper.findById(tableId)
                    .orElseThrow(() -> new IllegalArgumentException("未找到指定的表信息，ID：" + tableId));

            List<GenTableColumn> columns = genTableColumnMapper.findByTableId(tableId);

            GenTableDto dto = AutoMapperProfile.INSTANCE.toDto(table, columns);

            // 生成data 上下文
            Map<String, Object> ctx = FreeMarkerUtils.prepareConfiguration(table);

            List<String> templates = FreeMarkerUtils.getTemplateList();

            for (String template : templates) {
                // 渲染模板
                StringWriter sw = new StringWriter();
                Template tpl = CONFIGURATION.getTemplate(template);
                tpl.process(dto, sw);
                dataMap.put(template, sw.toString());
            }
            return dataMap;
        } catch (IOException | TemplateException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取数据源信息
     *
     * @param databaseId 数据源ID
     * @return 数据源
     */
    private GenDataSource getDataSource(Long databaseId) {
        return databaseId == null
                ? dataSourceMapper.findTop1ByDefaultDataSourceTrue()
                .orElseThrow(() -> new IllegalArgumentException("未找到默认数据源"))
                : dataSourceMapper.findById(databaseId)
                .orElseThrow(() -> new IllegalArgumentException("未找到指定数据源，ID：" + databaseId));
    }

    /**
     * 创建 JdbcClient
     *
     * @param dataSource 数据源
     * @return JdbcClient
     */
    @SuppressWarnings("unchecked")
    private JdbcClient createJdbcClient(GenDataSource dataSource) {
        try {
            Class<?> clazz = ClassUtils.forName(dataSource.getType().getClassName(), null);
            return JdbcClient.create(DataSourceBuilder
                    .create()
                    .type((Class<DataSource>) clazz)
                    .url(dataSource.getUrl())
                    .username(dataSource.getUsername())
                    .password(dataSource.getPassword())
                    .build());
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException("数据源类未找到：" + dataSource.getType().getClassName(), e);
        }
    }

}